#ifndef EspressifMQTTClient_H
#define EspressifMQTTClient_H

#include "Callback.h"
#include "IMQTTClient.h"
#include "esp_err.h"
#include "esp_log.h"
#include "mqtt_client.h"

#define MQTT_FAILURE_MESSAGE_ID -1;

class EspressifMQTTClient : public IMQTTClient {

private:
	Callback<void, char *, uint8_t *, unsigned int> m_received_data_callback =
		{};
	Callback<void> m_connected_callback = {};
	esp_mqtt_client_config_t mqtt_cfg = {};
	esp_mqtt_client_handle_t m_mqtt_client = {};
	bool m_connected = {};

	void mqtt_event_handler(esp_event_base_t base,
							esp_mqtt_event_id_t const &event_id,
							void *event_data) {
		esp_mqtt_event_handle_t const event =
			static_cast<esp_mqtt_event_handle_t>(event_data);
		switch (event_id) {
		case esp_mqtt_event_id_t::MQTT_EVENT_CONNECTED:
			m_connected = true;
			m_connected_callback.callCallback();
			break;
		case esp_mqtt_event_id_t::MQTT_EVENT_DISCONNECTED:
			m_connected = false;
			break;
		case esp_mqtt_event_id_t::MQTT_EVENT_DATA: {
			// Check wheter the given message has not bee received completly,
			// but instead would be received in multiple chunks, if it were we
			// discard the message because receiving a message over multiple
			// chunks is currently not supported
			if (event->data_len != event->total_data_len) {
				break;
			}
			// Topic is not null terminated, to fix this issue we copy the topic
			// string. This overhead is acceptable, because we nearly always
			// copy only a few bytes (around 20), meaning the overhead is
			// insignificant.
			char topic[event->topic_len + 1] = {};
			strncpy(topic, event->topic, event->topic_len);
			m_received_data_callback.callCallback(
				topic, reinterpret_cast<uint8_t *>(event->data),
				event->data_len);
			break;
		}
		default:
			// Nothing to do
			break;
		}
	}

public:
	EspressifMQTTClient() :
		m_received_data_callback(),
		m_connected_callback(),
		mqtt_cfg(),
		m_mqtt_client(nullptr),
		m_connected(false) {
		// Nothing to do
	}

	~EspressifMQTTClient() { (void)esp_mqtt_client_destroy(m_mqtt_client); }

	void setDataCallback(Callback<void, char *, uint8_t *,
									unsigned int>::function callback) override {
		m_received_data_callback.setCallback(callback);
	}

	void setConnectCallback(Callback<void>::function callback) override {
		m_connected_callback.setCallback(callback);
	}

	bool setBufferSize(uint16_t receive_buffer_size,
						 uint16_t send_buffer_size) {
		return false;
	}

	uint16_t getReceiveBufferSize() override { return 0U; }

	uint16_t getSendBufferSize() override { return 0U; }

	void setServer(char const *domain, uint16_t port) override {
		mqtt_cfg.broker.address.hostname = domain;
		mqtt_cfg.broker.address.port = port;
		mqtt_cfg.broker.address.transport = MQTT_TRANSPORT_OVER_TCP;
	}

	bool connect(char const *client_id, char const *user_name,
				 char const *password) override {
		mqtt_cfg.credentials.client_id = client_id;
		mqtt_cfg.credentials.username = user_name;
		mqtt_cfg.credentials.authentication.password = password;
		if (m_mqtt_client != nullptr) {
			const esp_err_t err = esp_mqtt_client_reconnect(m_mqtt_client);
			return err == ESP_OK;
		}

		m_mqtt_client = esp_mqtt_client_init(&mqtt_cfg);
		esp_err_t err = esp_mqtt_client_register_event(
			m_mqtt_client, MQTT_EVENT_ANY,
			EspressifMQTTClient::static_mqtt_event_handler, this);
		if (err != ESP_OK) {
			return false;
		}

		err = esp_mqtt_client_start(m_mqtt_client);
		return err == ESP_OK;
	}

	void disconnect() override {
		(void)esp_mqtt_client_disconnect(m_mqtt_client);
	}

	bool loop() override { return m_connected; }

	bool publish(char const *topic, char const *payload,
				 size_t const &length, int qos, int retain) override {
		if (!connected()) {
			return false;
		}
		int const message_id = esp_mqtt_client_publish(
			m_mqtt_client, topic, reinterpret_cast<const char*>(payload), length, qos, retain);
		return message_id > MQTT_FAILURE_MESSAGE_ID;
	}

	bool subscribe(char const *topic, int qos) override {
		if (!connected()) {
			return false;
		}
		int const message_id =
			esp_mqtt_client_subscribe(m_mqtt_client, topic, 0U);
		return message_id > MQTT_FAILURE_MESSAGE_ID;
	}

	bool unsubscribe(char const *topic) override {
		if (!connected()) {
			return false;
		}
		int const message_id =
			esp_mqtt_client_unsubscribe(m_mqtt_client, topic);
		return message_id > MQTT_FAILURE_MESSAGE_ID;
	}

	bool connected() override { return m_connected; }

	static void static_mqtt_event_handler(void *handler_args,
										  esp_event_base_t base,
										  int32_t event_id, void *event_data) {
		if (handler_args == nullptr) {
			return;
		}

		auto instance = static_cast<EspressifMQTTClient *>(handler_args);
		instance->mqtt_event_handler(
			base, static_cast<esp_mqtt_event_id_t>(event_id), event_data);
	}
};
#endif